<html><head><meta charset="utf-8"/><title>操作文件</title></head><body><h1>操作文件</h1><div class="x-wiki-content x-main-content">
 <p>
  在HTML表单中，可以上传文件的唯一控件就是
  <code>
   &lt;input type="file"&gt;
  </code>
  。
 </p>
 <p>
  <em>
   注意
  </em>
  ：当一个表单包含
  <code>
   &lt;input type="file"&gt;
  </code>
  时，表单的
  <code>
   enctype
  </code>
  必须指定为
  <code>
   multipart/form-data
  </code>
  ，
  <code>
   method
  </code>
  必须指定为
  <code>
   post
  </code>
  ，浏览器才能正确编码并以
  <code>
   multipart/form-data
  </code>
  格式发送表单的数据。
 </p>
 <p>
  出于安全考虑，浏览器只允许用户点击
  <code>
   &lt;input type="file"&gt;
  </code>
  来选择本地文件，用JavaScript对
  <code>
   &lt;input type="file"&gt;
  </code>
  的
  <code>
   value
  </code>
  赋值是没有任何效果的。当用户选择了上传某个文件后，JavaScript也无法获得该文件的真实路径：
 </p>
 <script>
  $(function () {
    var
        fileInput = document.getElementById('test-file-upload'),
        filePath = document.getElementById('test-get-filename');
    fileInput.addEventListener('change', function () {
        filePath.innerText = fileInput.value;
    });
});
 </script>
 <form action="http://localhost/test" enctype="multipart/form-data" method="post">
  <p>
   <input id="test-file-upload" name="test" type="file"/>
  </p>
  <p>
   待上传文件:
   <span id="test-get-filename" style="color:red">
   </span>
  </p>
 </form>
 <p>
  通常，上传的文件都由后台服务器处理，JavaScript可以在提交表单时对文件扩展名做检查，以便防止用户上传无效格式的文件：
 </p>
 <pre><code>var f = document.getElementById('test-file-upload');
var filename = f.value; // 'C:\fakepath\test.png'
if (!filename || !(filename.endsWith('.jpg') || filename.endsWith('.png') || filename.endsWith('.gif'))) {
    alert('Can only upload image file.');
    return false;
}
</code></pre>
 <h3>
  File API
 </h3>
 <p>
  由于JavaScript对用户上传的文件操作非常有限，尤其是无法读取文件内容，使得很多需要操作文件的网页不得不用Flash这样的第三方插件来实现。
 </p>
 <p>
  随着HTML5的普及，新增的File API允许JavaScript读取文件内容，获得更多的文件信息。
 </p>
 <p>
  HTML5的File API提供了
  <code>
   File
  </code>
  和
  <code>
   FileReader
  </code>
  两个主要对象，可以获得文件信息并读取文件。
 </p>
 <p>
  下面的例子演示了如何读取用户选取的图片文件，并在一个
  <code>
   &lt;div&gt;
  </code>
  中预览图像：
 </p>
 <script>
  $(function() {
    var fileInput = document.getElementById('test-image-file');
    var info = document.getElementById('test-file-info');
    var preview = document.getElementById('test-image-preview');
    fileInput.addEventListener('change', function () {
        preview.style.backgroundImage = '';
        if (!fileInput.value) {
            info.innerHTML = '没有选择文件';
            return;
        }
        var file = fileInput.files[0];
        info.innerHTML = '文件: ' + file.name + '<br>' +
                         '大小: ' + file.size + '<br>' +
                         '修改: ' + file.lastModifiedDate;
        if (file.type !== 'image/jpeg' && file.type !== 'image/png' && file.type !== 'image/gif') {
            alert('不是有效的图片文件!');
            return;
        }
        var reader = new FileReader();
        reader.onload = function(e) {
            var
                data = e.target.result;
                index = data.indexOf(';base64,');
            preview.style.backgroundImage = 'url(' + data + ')';
        };
        reader.readAsDataURL(file);
    });
});
 </script>
 <form action="http://localhost/test" enctype="multipart/form-data" method="post">
  <p>
   图片预览：
  </p>
  <p>
  </p>
  <div id="test-image-preview" style="border: 1px solid #ccc; width: 100%; height: 200px; background-size: contain; background-repeat: no-repeat; background-position: center center;">
  </div>
  <p>
   <input id="test-image-file" name="test" type="file"/>
  </p>
  <p id="test-file-info">
  </p>
 </form>
 <pre><code>var
    fileInput = document.getElementById('test-image-file'),
    info = document.getElementById('test-file-info'),
    preview = document.getElementById('test-image-preview');
// 监听change事件:
fileInput.addEventListener('change', function () {
    // 清除背景图片:
    preview.style.backgroundImage = '';
    // 检查文件是否选择:
    if (!fileInput.value) {
        info.innerHTML = '没有选择文件';
        return;
    }
    // 获取File引用:
    var file = fileInput.files[0];
    // 获取File信息:
    info.innerHTML = '文件: ' + file.name + '&lt;br&gt;' +
                     '大小: ' + file.size + '&lt;br&gt;' +
                     '修改: ' + file.lastModifiedDate;
    if (file.type !== 'image/jpeg' &amp;&amp; file.type !== 'image/png' &amp;&amp; file.type !== 'image/gif') {
        alert('不是有效的图片文件!');
        return;
    }
    // 读取文件:
    var reader = new FileReader();
    reader.onload = function(e) {
        var
            data = e.target.result; // '...(base64编码)...'            
        preview.style.backgroundImage = 'url(' + data + ')';
    };
    // 以DataURL的形式读取文件:
    reader.readAsDataURL(file);
});
</code></pre>
 <p>
  上面的代码演示了如何通过HTML5的File API读取文件内容。以DataURL的形式读取到的文件是一个字符串，类似于
  <code>
   ...(base64编码)...
  </code>
  ，常用于设置图像。如果需要服务器端处理，把字符串
  <code>
   base64,
  </code>
  后面的字符发送给服务器并用Base64解码就可以得到原始文件的二进制内容。
 </p>
 <h3>
  回调
 </h3>
 <p>
  上面的代码还演示了JavaScript的一个重要的特性就是单线程执行模式。在JavaScript中，浏览器的JavaScript执行引擎在执行JavaScript代码时，总是以单线程模式执行，也就是说，任何时候，JavaScript代码都不可能同时有多于1个线程在执行。
 </p>
 <p>
  你可能会问，单线程模式执行的JavaScript，如何处理多任务？
 </p>
 <p>
  在JavaScript中，执行多任务实际上都是异步调用，比如上面的代码：
 </p>
 <pre><code>reader.readAsDataURL(file);
</code></pre>
 <p>
  就会发起一个异步操作来读取文件内容。因为是异步操作，所以我们在JavaScript代码中就不知道什么时候操作结束，因此需要先设置一个回调函数：
 </p>
 <pre><code>reader.onload = function(e) {
    // 当文件读取完成后，自动调用此函数:
};
</code></pre>
 <p>
  当文件读取完成后，JavaScript引擎将自动调用我们设置的回调函数。执行回调函数时，文件已经读取完毕，所以我们可以在回调函数内部安全地获得文件内容。
 </p>
</div>
</body></html>